// CentralCache 中间层，加桶锁，单位 span(管理的都是以页为单位的大块内存)
// 主要负责功能：
// 1. 对下(ThreadCache) 进行空间对象的分配与回收
// 2. 对上(PageCache) 进行span 的申请与释放

#include "common.hpp"
#include "pageCache.hpp"

class CentralCache
 {
private:
    SpanList span_lists_array[CentralCache_SpanLists];  // 基于 span 的链表数组

    // 单例模式相关属性
    static CentralCache _sInst;
    CentralCache(){}
    CentralCache(const CentralCache&) = delete;
    CentralCache& operator()(const CentralCache&) = delete;
private:
    // 获取一个非空的 Span
    Span* getOneSpan(SpanList& slist, size_t alignSize)
    {
        // 1，先在 span_lists_array[index] 中寻找非空的span
        Span* it = slist.slistBegin();
        while (it != slist.slistEnd())
        {
            if(it->span_freelist != nullptr)
                return it;
            else
                it = it->next;
        }

        // 2.此时该 slist 下就是一个空的 span链表, 那么就只能往 PageCache 中申请
        slist.mtx.unlock(); // 解桶锁
        PageCache::getInstance()->getPageMutex().lock();    // 加大锁
        Span* newspan_frompagecache = PageCache::getInstance()->c_getKSpanFromPageCache(ObjFreelist_AlignMap::allocaPageFromPageCache(alignSize));
        newspan_frompagecache->objSize = alignSize; // 设置该 span 中小块内存的大小
        newspan_frompagecache->isUse = true; // 标记为正在使用
        PageCache::getInstance()->getPageMutex().unlock();  // 解大锁

        // 3.计算 newspan_frompagecache 的大块内存的起始地址和大块内存的字节数
        char* start = (char*)(newspan_frompagecache->page_id << Page_Shift);
        size_t get_all_bytes = newspan_frompagecache->n << Page_Shift;

        // 4. 将大块内存切成 size 大小的对象，并将它们链接起来
        // 4.1
        char* end = start + get_all_bytes;
        newspan_frompagecache->span_freelist = start;
        start += alignSize;
        // 4.2
        void* tail = newspan_frompagecache->span_freelist;
        while (start < end)
        {
            Util::nextObj(tail) = start;
            tail = start;
            start += alignSize;
        }
        Util::nextObj(tail) = nullptr;
        
        // 5. 将切好的span头插到 slist
        slist.mtx.lock();   // 加桶锁
        slist.insertSpanInPos(slist.slistBegin(), newspan_frompagecache);
        return newspan_frompagecache;
    }
public:
    // 设置成为单例模式，提供一个全局访问点
    static CentralCache* getInstance()
    {
        return &_sInst;
    } 
    // CentralCache 分配给 ThreadCache 一定数量的 内存对象
    size_t t_fetchRangeObj(void*& start, void*& end, size_t n, size_t alignSize)
    {
        size_t index = ObjFreelist_AlignMap::hashIndex(alignSize);
        span_lists_array[index].mtx.lock(); // 加桶锁
        // 1.在 对应(下标为index) 的哈希桶中 获取一个 非空的span
        Span* assign_span = getOneSpan(span_lists_array[index], alignSize);
        assert(assign_span);
        assert(assign_span->span_freelist);

        // 2.从 Span 中获取 n 个 内存对象(大小为 alignSize)
        // 如果不够 n 个， 有多少就拿多少
        start = assign_span->span_freelist;
        end = assign_span->span_freelist;
        size_t actualNum = 1;
        while (Util::nextObj(end) && n - 1)
        {
            end = Util::nextObj(end);
            actualNum++;
            n--;
        }
        assign_span->span_freelist = Util::nextObj(end);    // 取完后剩下的对象继续放在 自由链表中
        Util::nextObj(end) = nullptr;
        assign_span->use_count += actualNum;    // 更新分配给 threadCache 的计数

        // 3.解锁
        span_lists_array[index].mtx.unlock();
        return actualNum;
    }
    // ThreadCache 释放内存对象到 CentralCache【释放对象导致链表过长，回收内存到中心缓存】
    void t_retRangeObjToCentralCache(void* start, size_t bytes)
    {
        size_t index = ObjFreelist_AlignMap::hashIndex(bytes);
        span_lists_array[index].mtx.lock(); // 加桶锁
        while(start)    // 一个一个的遍历 ThreadCache 返回来的内存对象，分别将她们放入对应的 Span 位置
        {
            void* next = Util::nextObj(start);
            Span* span = PageCache::getInstance()->objToSpanMap(start); 
            // 将对象头插到 span 的自由链表
            Util::nextObj(start) = span->span_freelist;
            span->span_freelist = start;
            span->use_count--;  // 更新被分配给 threadCache 的内存对象的计数

            if(span->use_count == 0){   // 说明这个 span 分配出去的 内存对象已经全部都回来了
                // 那么此时这个 span 就可以再回收到 PageCache , PageCache 再去做前后页的合并
                span_lists_array[index].eraseSpanInPos(span);
                span->span_freelist = nullptr;
                span->next = nullptr;
                span->prev = nullptr;

                // 释放sapn给 PageCache ，使用PageCache 的大锁,不过这时需要将 桶锁给解掉
                span_lists_array[index].mtx.unlock();
                PageCache::getInstance()->getPageMutex().lock();
                PageCache::getInstance()->c_retSpanToPageCache(span);
                PageCache::getInstance()->getPageMutex().unlock();
                span_lists_array[index].mtx.lock(); // 重新加上桶锁
            }
            start = next;
        }
        span_lists_array[index].mtx.unlock();   // 再解桶锁
    }
};

CentralCache CentralCache::_sInst;